package cn.demoncat.util.web;

import cn.demoncat.util.lang.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.BeanExpressionResolver;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.ServletContextEvent;
import java.lang.annotation.Annotation;
import java.util.Map;
import java.util.function.Consumer;

/**
 * 描述：Spring工具类
 * 
 * 1、Spring容器加载时，会调用setApplicationContext()，之后便可以使用@Resource注入SpringUtil然后在运行时获取Bean
 *   
 * 2、使用SpringUtil的static方法也可以获取ApplicationContext，如测试时通过XML获取(不建议)、通过Servlet监听器获取等
 *
 * @see	ApplicationContextAware	应用初始化回调
 * @see BeanFactoryAware		Bean工厂回调
 *
 * @author 延晓磊
 *
 * @since 2017年7月23日
 */
@Slf4j
@Component("springUtil")
public class SpringUtil implements ApplicationContextAware, BeanFactoryAware {

	// Spring容器
	private ApplicationContext applicationContext;
	// Bean工厂
	private ConfigurableListableBeanFactory beanFactory;
	// Bean表达式解析器
	private BeanExpressionResolver beanExpressionResolver;
	// Bean表达式Context
	private BeanExpressionContext beanExpressionContext;
	// Bean表达式Scope
	private final BeanExpressionScope beanExpressionScope = new BeanExpressionScope();

	
	/**
	 * 功能描述：运行时获取ApplicationContext
	 *
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月23日
	 */
	public ApplicationContext getContext(){
		return this.applicationContext;
	}
	 
	/**
	 * 功能描述：运行时通过name获取 Bean
	 *
	 * @param name
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月21日
	 */
    public Object getBean(String name){
       return this.applicationContext.getBean(name);
    }

    /**
	 * 功能描述：运行时通过name获取 Bean
	 *
	 * @param name
	 * @param clazz 指定Bean的返回类型
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年7月21日
	 */
    public <T>T getBean(String name, Class<T> clazz){
        return this.applicationContext.getBean(name, clazz);
     }

	/**
	 * 功能描述：运行时通过class获取 Bean，如果存在则执行操作
	 *
	 * @param name
	 * @param clazz
	 * @param consumer	Bean存在时的操作
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2017年7月21日
	 */
	public <T>void getBean(String name, Class<T> clazz, Consumer<T> consumer){
		T bean;
		try {
			// 获取Bean
			bean = this.getBean(name, clazz);
		}catch (Exception e){
			// 获取Bean失败
			bean = null;
		}
		// 使用Bean
		if (bean != null) {
			consumer.accept(bean);
		}
	}
    
    /**
     * 功能描述：运行时通过class获取 Bean
	 *
     * @param clazz
     * @return
     * 
     * @author 延晓磊
     *
     * @since 2017年7月21日
     */
    public <T> T getBean(Class<T> clazz){
       return this.applicationContext.getBean(clazz);
    }

	/**
	 * 功能描述：运行时通过class获取 Bean，如果存在则执行操作
	 *
	 * @param clazz
	 * @param consumer	Bean存在时的操作
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2017年7月21日
	 */
	public <T>void getBean(Class<T> clazz, Consumer<T> consumer){
		T bean;
		try {
			// 获取Bean
			bean = this.getBean(clazz);
		}catch (Exception e){
		   // 获取Bean失败
			bean = null;
		}
		// 使用Bean
		if (bean != null) {
			consumer.accept(bean);
		}
	}

    /**
     * 功能描述：运行时通过class获取 Beans
	 *
     * @param clazz
     * @return
     * 
     * @author 延晓磊
     *
     * @since 2017年7月21日
     */
    public <T> Map<String, T> getBeans(Class<T> clazz){
       return this.applicationContext.getBeansOfType(clazz);
    }

	/**
	 * 功能描述：运行时通过class获取 Beans
	 *
	 * @param annotation
	 * @return
	 *
	 * @author 延晓磊
	 *
	 * @since 2017年7月21日
	 */
	public Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotation){
		return this.applicationContext.getBeansWithAnnotation(annotation);
	}

	/**
	 * 解析表达式
	 *
	 * 例：#{mqUtil.getTopic(123)} > prefix-123
	 * 例：#{mqUtil.getTopic('abc')} > prefix-abc
	 * 例：#{mqUtil.getTopic('${spring.application.name}_job')} > prefix-app_job
	 * 例：#{mqUtil.getPrefix()}${spring.application.name}_job > prefix-app_job
	 * 例：#{appProperties.name}-topic > app-topic
	 * 例：topic-#{T(java.lang.System).currentTimeMillis()} > topic-1578728739402
	 * 例：#{'group-' + T(cn.demoncat.util.web.IpUtil).LOCAL_IP} > group-192.168.1.1
	 * 例：${spring.application.name} > app
	 * 例：topic-${server.port} > topic-8080
	 *
	 * @param value		SpEL表达式：#{}
	 * @return
	 *
	 * @author 延晓磊
	 * @since 2023年04月24日
	 */
	public String resolve(String value) {
		// BeanFactory未初始化
		if (this.beanFactory == null) {
			log.warn("BeanFactor未初始化，所以解析失败：" + value);
		 	return value;
		}
		// SpEL解析
		return StringUtil.toEmptyString(this.beanExpressionResolver.evaluate(this.beanFactory.resolveEmbeddedValue(value), this.beanExpressionContext));
	}

    /**
	 * 保存ApplicationContext(在Spring容器装载完成时由系统调用)
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年3月23日 
	 */
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) {
		this.applicationContext = applicationContext;
	}

	/**
	 * 保存BeanFactory(在Spring容器装载完成时由系统调用)
	 *
	 * @param beanFactory
	 *
	 * @author 延晓磊
	 * @since 2023年04月24日
	 */
	@Override
	public void setBeanFactory(BeanFactory beanFactory) {
		if (beanFactory instanceof ConfigurableListableBeanFactory) {
			this.beanFactory = (ConfigurableListableBeanFactory)beanFactory;
			this.beanExpressionResolver = this.beanFactory.getBeanExpressionResolver();
			this.beanExpressionContext = new BeanExpressionContext(this.beanFactory, this.beanExpressionScope);
		}
	}

	/**
	 * 功能描述：通过加载XML文件获取Spring容器。
	 * 
	 * 1、可以在任何时侯、任何地方调用
	 * 2、适用于传统Spring项目的测试阶段，调用时会重新加载Spring容器，所以不要在运行中调用
	 *
	 * @param xmlPath Spring的XML配置路径，如"spring/applicationContext-core.xml"
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年3月23日
	 */
	public static ApplicationContext getContext(String ...xmlPath){
		String[] configs = new String[xmlPath.length];
		for (int i = 0; i < configs.length; i++) {
			configs[i] = "classpath:"+xmlPath[i];
		}
		return new FileSystemXmlApplicationContext(configs);
	}
	
	/**
	 * 功能描述：获取Spring容器。
	 * 
	 * 用于Spring容器初始化完成的事件监听器的onApplicationEvent(ContextRefreshedEvent event)方法中执行。
	 * Spring容器初始化完成监听器示例：
	 * <pre>@Component public class InitMedia implements ApplicationListener<ContextRefreshedEvent>,Ordered{ } </pre>
	 * 
	 * @param event Spring容器初始化完成事件
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年3月23日
	 */
	public static ApplicationContext getContext(ContextRefreshedEvent event){
		return event.getApplicationContext();
	}
	
	/**
	 * 功能描述：获取Spring容器。
	 * 
	 * 用于Servlet容器的Listener的 contextInitialized(ServletContextEvent servlet)方法中执行。
	 * Servlet容器监听器示例：
	 * 1、在XML中配置Listener；
	 * 2、public class AppContextListener implements ServletContextListener {}
	 *
	 * @param servlet
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年3月23日
	 */
	public static ApplicationContext getContext(ServletContextEvent servlet){
		 return  WebApplicationContextUtils.getRequiredWebApplicationContext(servlet.getServletContext());
	}

	/**
	 * BeanExpression 解析作用
	 *
	 * @author 延晓磊
	 * @since 2023年04月24日
	 */
	private static class BeanExpressionScope implements Scope {

		@Override
		public Object get(String name, ObjectFactory<?> objectFactory) {
			 return null;
		}

		@Override
		public Object remove(String name) {
			return null;
		}

		@Override
		public void registerDestructionCallback(String name, Runnable callback) {
		}

		@Override
		public Object resolveContextualObject(String key) {
			return null;
		}

		@Override
		public String getConversationId() {
			return null;
		}
	}
}
 
